﻿using System;
using System.Collections.Generic;
using System.IO;


/***********************************************************/
// message head
//     id           state check protocol frame      message
// |                |    |    |                |    ...    ...   ...
// 0                8    10   12               20
/***********************************************************/

namespace Sttplay.Net.UDP
{
    public class RCTP
    {
        public enum PackageState
        {
            PSH,
            ACK,
            BKACK,
            RMACK,
            ENDACK
        }
        public enum SendState
        {
            Success = 1,
            Before = 0,
            Timeout = -1,
            MaxTimeout = -2
        };

        public enum Option
        {
            MAX_RESENT_GROUP_COUNT,
            DEFAULT_TIMEOUT,
            MULTIPLE,
            MAX_RESEND_TIMEOUT,
            RESET_TIME,
        }

        public class Message
        {
            public ulong id;
            public PackageState state;
            public object param;
            public uint sendTs;
            public uint waitTimeout;
            public byte[] data;
            public int resendCount;
            public void OnSendEvent(RCTP rctp, SendState state)
            {
                if (rctp.onSend != null)
                    rctp.onSend(this, state);
            }
        }

        /// <summary>
        /// 最大重传组次数
        /// </summary>
        private uint MAX_RESEND_GROUP_COUNT = 4;

        /// <summary>
        /// 默认超时时间
        /// </summary>
        private uint DEFAULT_TIMEOUT = 200;

        /// <summary>
        /// 重传时间倍率
        /// </summary>
        private float Multiple = 1.5f;

        /// <summary>
        /// 超时时间
        /// </summary>
        private uint MAX_RESEND_TIMEOUT = 2000;

        /// <summary>
        /// 超时是否重置时间
        /// </summary>
        private bool resetTime = true;

        /// <summary>
        /// 真正的发送函数回调
        /// </summary>
        public Action<byte[], int, int, object> SendImp { set; private get; }

        public const int HEAD_SIZE = 20;
        public const int BUFF_SIZE = 1400;
        private ushort id;
        private Dictionary<ulong, Message> sendMsgDict = new Dictionary<ulong, Message>();
        private Dictionary<ulong, Message> recvMsgDict = new Dictionary<ulong, Message>();
        private ulong sendframe, recvframe;
        private bool nextClear = false;
        /// <summary>
        /// 接收到消息
        /// </summary>
        public event Action<byte[], bool, object> onRecv;

        /// <summary>
        /// 发送消息是否成功
        /// </summary>
        public event Action<Message, SendState> onSend;
        public static event Action<string> onErrorLog;
        public RCTP()
        {

        }

        /// <summary>
        /// 初始化 RCTP
        /// </summary>
        public void Initialize()
        {
            Terminate();
            id = RandomUint16();
        }

        /// <summary>
        /// 设置选项
        /// </summary>
        /// <param name="option"></param>
        /// <param name="value"></param>
        public void SetOption(Option option, uint value)
        {
            switch (option)
            {
                case Option.MAX_RESENT_GROUP_COUNT:
                    MAX_RESEND_GROUP_COUNT = value;
                    break;
                case Option.DEFAULT_TIMEOUT:
                    if (value < 50)
                        value = 50;
                    DEFAULT_TIMEOUT = value;
                    break;
                case Option.MAX_RESEND_TIMEOUT:
                    if (value < 2000)
                        value = 2000;
                    MAX_RESEND_TIMEOUT = value;
                    break;
                case Option.RESET_TIME:
                    resetTime = value != 0;
                    break;
            }
        }

        public void SetOption(Option option, float value)
        {
            switch (option)
            {
                case Option.MULTIPLE:
                    if (value < 1.1f)
                        value = 1.1f;
                    Multiple = value;
                    break;
            }
        }

        /// <summary>
        /// 设置下次发送的帧的ProtocolFrame
        /// </summary>
        /// <param name="frame"></param>
        public void SetProtocolFrame(ulong frame)
        {
            sendframe = frame;
        }

        /// <summary>
        /// 获取当前接收帧的ProtocolFrame
        /// </summary>
        /// <returns></returns>
        public ulong GetProtocolFrame()
        {
            return recvframe;
        }

        /// <summary>
        /// 驱动
        /// </summary>
        public void Update(int timeout = 0)
        {
            System.Threading.Thread.Sleep(timeout);
            HandleResend(sendMsgDict);
            HandleResend(recvMsgDict);
        }

        /// <summary>
        /// 处理重发
        /// </summary>
        /// <param name="dict">目标Dict</param>
        private void HandleResend(Dictionary<ulong, Message> dict)
        {
            uint ts = GetTimestamp();
            List<ulong> removes = new List<ulong>();
            foreach (var item in dict)
            {
                Message msg = item.Value;
                if (ts - msg.sendTs > MAX_RESEND_TIMEOUT)
                {
                    if (msg.resendCount >= MAX_RESEND_GROUP_COUNT)
                    {
                        msg.OnSendEvent(this, SendState.MaxTimeout);
                        if (nextClear)
                            break;
                        removes.Add(msg.id);
                    }
                    else
                    {
                        if (resetTime)
                        {
                            msg.waitTimeout = DEFAULT_TIMEOUT;
                            msg.sendTs = GetTimestamp();
                            SendTo(msg.data, 0, msg.data.Length, msg.param);
                            if (nextClear)
                                break;
                            msg.resendCount++;
                        }
                        else if (ts - msg.sendTs > msg.waitTimeout)
                        {
                            msg.waitTimeout = ts - msg.sendTs + MAX_RESEND_TIMEOUT;
                            msg.OnSendEvent(this, SendState.Timeout);
                            SendTo(msg.data, 0, msg.data.Length, msg.param);
                            msg.resendCount++;
                        }
                    }
                }
                else if (ts - msg.sendTs > msg.waitTimeout)
                {
                    msg.waitTimeout = (uint)(msg.waitTimeout * Multiple);
                    SendTo(msg.data, 0, msg.data.Length, msg.param);
                }
            }
            for (int i = 0; i < removes.Count; i++)
                dict.Remove(removes[i]);
            if (nextClear)
            {
                nextClear = false;
                Terminate();
            }
        }

        public void Clear()
        {
            nextClear = true;
        }

        private void HandleBuffer(ulong id, PackageState state, ulong frame, byte[] buffer, object param)
        {
            if (state == PackageState.PSH)
            {
                if (!recvMsgDict.ContainsKey(id))
                {
                    recvframe = frame;
                    OnRecvEvent(buffer, true, param);
                    SendInternalACK(id, PackageState.ACK, recvMsgDict, param);
                }
            }
            else if (state == PackageState.ACK)
            {
                if (sendMsgDict.ContainsKey(id))
                {
                    sendMsgDict[id].OnSendEvent(this, SendState.Success);
                    sendMsgDict.Remove(id);
                }
                byte[] data = AssemblyBuffer(id, PackageState.BKACK, sendframe);
                SendTo(data, 0, data.Length, param);
            }
            else if (state == PackageState.BKACK)
            {
                if (recvMsgDict.ContainsKey(id))
                {
                    recvMsgDict.Remove(id);
                    SendInternalACK(id, PackageState.RMACK, recvMsgDict, param);
                } 
            }
            else if(state == PackageState.RMACK)
            {
                byte[] data = AssemblyBuffer(id, PackageState.ENDACK, sendframe);
                SendTo(data, 0, data.Length, param);
            }
            else if(state == PackageState.ENDACK)
            {
                if (recvMsgDict.ContainsKey(id))
                    recvMsgDict.Remove(id);
            }
        }

        /// <summary>
        /// 解析原始数据
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="index"></param>
        /// <param name="len"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        public void InputOriginRecv(byte[] buffer, int index, int len, object param)
        {
            if (VerifyBuffer(buffer, index, len))
            {
                ulong id = BitConverter.ToUInt64(buffer, 0);
                PackageState state = (PackageState)BitConverter.ToUInt16(buffer, 8);
                byte[] temp = new byte[len - HEAD_SIZE];
                Buffer.BlockCopy(buffer, HEAD_SIZE, temp, 0, temp.Length);
                ulong frame = BitConverter.ToUInt64(buffer, 12);
                HandleBuffer(id, state, frame, temp, param);
            }
            else
            {
                byte[] temp = new byte[len];
                Buffer.BlockCopy(buffer, 0, temp, 0, len);
                OnRecvEvent(temp, false, param);
            }
        }

        private void OnRecvEvent(byte[] buffer, bool reliable, object param)
        {
            if (onRecv != null)
                onRecv(buffer, reliable, param);
        }
        private static void OnErrorLogEvent(string error)
        {
            if (onErrorLog != null)
                onErrorLog(error);
        }
        public static uint GetTimestamp()
        {
            //获取从1970年一月一日0点0分0秒0微妙开始
            ulong ms = (ulong)(DateTime.UtcNow.Ticks - new DateTime(1970, 1, 1, 0, 0, 0, 0).Ticks) / 10000;
            return (uint)(ms & 0xfffffffful);
        }
        private bool VerifyBuffer(byte[] buffer, int index, int length)
        {
            if (length < HEAD_SIZE)
                return false;
            int sum = 0;
            for (int i = 0; i < 10; i++)
                sum += buffer[i + index];
            if (sum != BitConverter.ToUInt16(buffer, 10 + index))
                return false;
            return true;
        }

        private byte[] AssemblyBuffer(ulong id, PackageState state, ulong frame)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                byte[] idbytes = BitConverter.GetBytes(id);
                byte[] statebytes = BitConverter.GetBytes((ushort)state);
                ushort sum = 0;
                for (int i = 0; i < 8; i++)
                    sum += idbytes[i];
                for (int i = 0; i < 2; i++)
                    sum += statebytes[i];
                ms.Write(idbytes, 0, sizeof(ulong));
                ms.Write(statebytes, 0, sizeof(ushort));
                ms.Write(BitConverter.GetBytes(sum), 0, sizeof(ushort));
                ms.Write(BitConverter.GetBytes(frame), 0, sizeof(ulong));
                ms.Position = 0;
                byte[] buffer = new byte[ms.Length];
                ms.Read(buffer, 0, buffer.Length);
                return buffer;
            }
        }
        private byte[] AssemblyBuffer(ulong id, PackageState state, ulong frame, byte[] data, int dataOffset, int dataLen)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                ms.Write(AssemblyBuffer(id, state, frame), 0, HEAD_SIZE);
                ms.Write(data, dataOffset, dataLen);
                ms.Position = 0;
                byte[] buffer = new byte[ms.Length];
                ms.Read(buffer, 0, buffer.Length);
                return buffer;
            }
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="buffer">buffer</param>
        /// <param name="remote">end point</param>
        /// <param name="reliable">use small rudp</param>
        public void SendTo(byte[] buffer, int index, int len, bool reliable, object param)
        {
            if (reliable)
                SendInternal(buffer, index, len, param);
            else
                SendTo(buffer, index, len, param);
        }

        private void SendTo(byte[] buffer, int index, int len, object param)
        {
            if (SendImp != null)
                SendImp(buffer, index, len, param);
        }
        private void SendInternal(byte[] buffer, int index, int len, object param)
        {
            if (len > BUFF_SIZE)
                throw new Exception(string.Format("buffer length ({0}) > MAX_BUFF_SIZE {1}", buffer.Length, BUFF_SIZE));

            Message msg = new Message();
            msg.id = AssemblyID(GetTimestamp(), RandomUint16(), id++);
            msg.waitTimeout = DEFAULT_TIMEOUT;
            msg.sendTs = GetTimestamp();
            msg.param = param;
            msg.state = PackageState.PSH;
            msg.data = AssemblyBuffer(msg.id, msg.state, sendframe, buffer, index, len);
            msg.resendCount = 0;
            sendMsgDict.Add(msg.id, msg);
            msg.OnSendEvent(this, SendState.Before);
            SendTo(msg.data, 0, msg.data.Length, param);

        }

        private void SendInternalACK(ulong id, PackageState state, Dictionary<ulong, Message> map, object param)
        {
            Message msg = new Message();
            msg.id = id;
            msg.waitTimeout = DEFAULT_TIMEOUT;
            msg.sendTs = GetTimestamp();
            msg.param = param;
            msg.state = state;
            msg.data = AssemblyBuffer(msg.id, msg.state, sendframe);
            msg.resendCount = 0;
            map.Add(msg.id, msg);
            msg.OnSendEvent(this, SendState.Before);
            SendTo(msg.data, 0, msg.data.Length, param);
        }
        private static ushort RandomUint16()
        {
            return (ushort)new Random(Guid.NewGuid().GetHashCode()).Next(0, 65535);
        }
        public static float RandomSingle()
        {
            return (float)new Random(Guid.NewGuid().GetHashCode()).NextDouble();
        }
        private ulong AssemblyID(uint ts, ushort r, ushort lid)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                ms.Write(BitConverter.GetBytes(ts), 0, sizeof(uint));
                ms.Write(BitConverter.GetBytes(r), 0, sizeof(ushort));
                ms.Write(BitConverter.GetBytes(lid), 0, sizeof(ushort));
                ms.Position = 0;
                byte[] buffer = new byte[ms.Length];
                ms.Read(buffer, 0, buffer.Length);
                return BitConverter.ToUInt64(buffer, 0);
            }
        }


        /// <summary>
        /// 终止 RCTP
        /// </summary>
        public void Terminate()
        {
            recvMsgDict.Clear();
            sendMsgDict.Clear();
        }
    }


}